﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using EthernetTools.DataBindinds;
using EthernetTools.Utils;
using System.Drawing;

namespace EthernetTools.Protocols.PPPoE
{
    public class PPPoETagsContainer
    {
        Dictionary<PPPoETag.TagType, PPPoETag> tags = new Dictionary<PPPoETag.TagType, PPPoETag>();

        Dictionary<int, Dictionary<PPPoETag.TagType, PPPoETag>> tagsMap =
            new Dictionary<int, Dictionary<PPPoETag.TagType, PPPoETag>>();
        
        private TreeView TvGUI;

        private int currentPPPoeType = 0;

        public bool suppressEvents = false;

        private String2ByteDataBinding HostUniqueId;

        private String2ByteDataBinding ACName;

        #region Properties

        public PPPoETag this[PPPoETag.TagType type]
        {
            get
            {
                if (tags.ContainsKey(type))
                {
                    return tags[type];
                }
                return null;
            }
        }

        #endregion

        #region Constructors and Destructors

        public PPPoETagsContainer()
        {
        }

        #endregion

        #region Public Methods

        public void Init(TreeView tv, int ppoetype = -1)
        {
            Release();

            if (tv != null)
            {
                InitTreeView(tv);

                UpdateTreeView();

                if (ppoetype != -1)
                {
                    tagsMap.Add(ppoetype, tags);
                }
            }
        }

        /// <summary>
        /// Clean UP! Important
        /// </summary>
        public void Release()
        {
            if (TvGUI != null)
            {
                TvGUI.AfterCheck -= TvGUI_AfterCheck;

                TvGUI.NodeMouseDoubleClick -= TvGUI_NodeMouseDoubleClick;

                TvGUI.AfterExpand -= TvGUI_AfterExpand;

                TvGUI.AfterCollapse -= TvGUI_AfterCollapse;

                this.TvGUI.DrawMode = System.Windows.Forms.TreeViewDrawMode.Normal;

                this.TvGUI.DrawNode -= TvGUI_DrawNode;
            }
        }

        /// <summary>
        /// Set ppoeTags according to pppoe type and input bindings
        /// </summary>
        /// <param name="pppoeType">Type of pppoe packet (PADI, PADO, etc...)</param>
        /// <param name="HostUniqueId">Binding to hostUid/param>
        /// <param name="ACName">Binding to AC name</param>
        /// <param name="ServiceNameText">Binding to Service name</param>
        public void SetPPPoeTags(int pppoeType, 
            String2ByteDataBinding hostUniqueId,
            String2ByteDataBinding aCName,
            String2ByteDataBinding ServiceNameText = null)
        {
            if (this.HostUniqueId == null)
            {
                this.HostUniqueId = hostUniqueId;
            }
            if (this.ACName == null)
            {
                this.ACName = aCName;
            }

            currentPPPoeType = pppoeType;

            if (tagsMap.ContainsKey(currentPPPoeType))
            {
                tags = tagsMap[currentPPPoeType];
            }
            else
            {
                tags = new Dictionary<PPPoETag.TagType, PPPoETag>();
            }

            // приходится вызывать явно т.к. повешены на них текстбоксы
            UpdateTag(PPPoETag.TagType.HostUniq, HostUniqueId.RawData, false);
            UpdateTag(PPPoETag.TagType.ACName, ACName.RawData, false);

            switch (pppoeType)
            {
                case PppoeProtocolPacket.PPPoEType.PADT:
                {
                    break;
                }
                case PppoeProtocolPacket.PPPoEType.PADI:
                {
                    AddTag(PPPoETag.TagType.ServiceName, ServiceNameText, false);
                    AddTag(PPPoETag.TagType.HostUniq, HostUniqueId, false);
                    
                    break;
                }
                case PppoeProtocolPacket.PPPoEType.PADO:
                {
                    AddTag(PPPoETag.TagType.ServiceName, ServiceNameText, false);
                    AddTag(PPPoETag.TagType.ACName, ACName, false);
                    
                    break;
                }
                case PppoeProtocolPacket.PPPoEType.PADR:
                {
                    AddTag(PPPoETag.TagType.ServiceName, ServiceNameText, false);
                    AddTag(PPPoETag.TagType.HostUniq, HostUniqueId, false);
                    
                    break;
                }
                case PppoeProtocolPacket.PPPoEType.PADS:
                {
                    AddTag(PPPoETag.TagType.ServiceName, ServiceNameText, false);
                    AddTag(PPPoETag.TagType.HostUniq, HostUniqueId, false);
                    AddTag(PPPoETag.TagType.ACName, ACName, false);

                    break;
                }
            }
            UpdateTreeView();
        }

        public byte[] GetPlayLoadLength()
        {
            byte[] retval = new byte[2] { 0x0, 0x0 };
            
            byte[] pppoeTags = this.ToBytes();

            if (pppoeTags.Length > 255)
            {
                retval[0] = (byte)(pppoeTags.Length / 255);
                retval[1] = (byte)(pppoeTags.Length % 255);
            }
            else
            {
                retval[1] = (byte)pppoeTags.Length;
            }

            return retval;
        }

        /// <summary>
        /// Adds tag
        /// </summary>
        /// <param name="type">Tag type</param>
        /// <param name="data">Tag string data</param>
        /// <param name="pMethod">String data parsing type</param>
        public void AddTag(PPPoETag.TagType type, string data, ByteParseStringMethod pMethod = ByteParseStringMethod.String2ByteConverting, bool updateTV = true)
        {
            AddTag(type, ByteUtils.ParseBytes(data, pMethod), updateTV);
        }

        /// <summary>
        /// Adds tag
        /// </summary>
        /// <param name="type">Tag type</param>
        /// <param name="binding">binding to data</param>
        public void AddTag(PPPoETag.TagType type, String2ByteDataBinding binding, bool updateTV = true)
        {
            if (binding != null)
            {
                AddTag(type, binding.RawData, updateTV);
            }
            else
            {
                AddTag(type, (byte[])null, updateTV);
            }
        }

        /// <summary>
        /// Adds tag
        /// </summary>
        /// <param name="type">Tag type</param>
        /// <param name="data">Tag's data</param>
        public void AddTag(PPPoETag.TagType type, byte[] data, bool updateTV = true)
        {
            if (!tags.ContainsKey(type))
            {
                tags.Add(type, new PPPoETag(type, data));
            }
            else
            {
                if (!tags[type].IsEnabled)
                {
                    tags[type].IsEnabled = true;
                }

                if (!ByteUtils.Equals(tags[type].Data, data))
                {
                    tags[type] = new PPPoETag(type, data, tags[type].IsExpanded);
                }
            }

            if (tagsMap.ContainsKey(currentPPPoeType))
            {
                tagsMap[currentPPPoeType] = tags;
            }
            else
            {
                tagsMap.Add(currentPPPoeType, new Dictionary<PPPoETag.TagType, PPPoETag> (tags));
            }

            if (updateTV)
            {
                UpdateTreeView();
            }

        }

        /// <summary>
        /// Updating tag. IF IT EXIST
        /// </summary>
        /// <param name="type"></param>
        /// <param name="data"></param>
        public void UpdateTag(PPPoETag.TagType type, byte[] data, bool updateTV = true)
        {
            if (tags.ContainsKey(type))
            {
                if (!ByteUtils.Equals(tags[type].Data, data))
                {
                    tags[type] = new PPPoETag(type, data, tags[type].IsExpanded);
                }
                if (updateTV)
                {
                    UpdateTreeView();
                }
            }

        }

        public static PPPoETagsContainer ParseFromBytes(byte[] tags)
        {
            PPPoETagsContainer ret = new PPPoETagsContainer();

            byte[] ppoeTag;
            byte[] taglen;
            byte[] tag;
            while (true)
            {

                ppoeTag = ByteUtils.RemoveBytes(2, ref tags);
                if (tags.Length <= 0)
                    break;

                taglen = ByteUtils.RemoveBytes(2, ref tags);
                if (tags.Length <= 0)
                    break;
                
                int tagType = ((ppoeTag[0] << 8) | ppoeTag[1]);
                int len = ((taglen[0] << 8) | taglen[1]);

                if (len > 0)
                {
                    tag = ByteUtils.RemoveBytes((uint)len, ref tags);

                    ret.AddTag((EthernetTools.Protocols.PPPoE.PPPoETag.TagType)tagType, tag);
                }

                if (tags.Length <= 0)
                    break;
            }

            return ret;
        }

        /// <summary>
        /// Returns raw bytes of all tags
        /// </summary>
        /// <returns></returns>
        public byte[] ToBytes()
        {
            byte[] retval = new byte[0];
            foreach (KeyValuePair<PPPoETag.TagType, PPPoETag> pt in tags)
            {
                if (pt.Value.IsEnabled)
                {
                    ByteUtils.AppendBytes(ref retval, pt.Value.Data);
                }
            }

            return retval;
        }
        #endregion

        #region Methods

        void InitTreeView(TreeView tv)
        {
            TvGUI = tv;

            foreach (string s in Enum.GetNames(typeof(PPPoETag.TagType)))
            {
                TreeNode tn = TvGUI.Nodes.Add(s);
                tn.Checked = false;
            }

            TvGUI.AfterCheck += new TreeViewEventHandler(TvGUI_AfterCheck);

            TvGUI.NodeMouseDoubleClick += new TreeNodeMouseClickEventHandler(TvGUI_NodeMouseDoubleClick);

            TvGUI.AfterExpand += new TreeViewEventHandler(TvGUI_AfterExpand);

            TvGUI.AfterCollapse += new TreeViewEventHandler(TvGUI_AfterCollapse);

            this.TvGUI.DrawMode = System.Windows.Forms.TreeViewDrawMode.OwnerDrawAll;

            this.TvGUI.DrawNode += new DrawTreeNodeEventHandler(TvGUI_DrawNode);

 
        }
       
        void UpdateTreeView()
        {
            if (TvGUI == null)
                return;
            if (suppressEvents)
                return;

            suppressEvents = true;

            TvGUI.Nodes.Clear();

            foreach (string s in Enum.GetNames(typeof(PPPoETag.TagType)))
            {
                PPPoETag.TagType tt = (PPPoETag.TagType)Enum.Parse(typeof(PPPoETag.TagType), s);

                if (tags.ContainsKey(tt))
                {
                    if (tags[tt].IsEnabled)
                    {
                        TreeNode tn = TvGUI.Nodes.Add(tags[tt].ThisType.ToString());
                        tn.Checked = true;
                        tn.Nodes.Add("Length:" + tags[tt].ValueLength()).Checked = true;
                        tn.Nodes.Add("Data:" + tags[tt].StringData()).Checked = true;
                        if (tags[tt].IsExpanded)
                        {
                            tn.ExpandAll();
                        }
                    }
                    else
                    {
                        TreeNode tn = TvGUI.Nodes.Add(tags[tt].ThisType.ToString());
                        tn.Checked = false;
                    }
                }
                else
                {
                    TreeNode tn = TvGUI.Nodes.Add(tt.ToString());
                    tn.Checked = false;
                }
            }

            suppressEvents = false;
        }

        void TvGUI_DrawNode(object sender, DrawTreeNodeEventArgs e)
        {
            if (e.Node.Parent != null)
            {
                Color backColor, foreColor;
                Rectangle nodeBounds = e.Node.Bounds;
                nodeBounds.Location = new Point(nodeBounds.Location.X - 20, nodeBounds.Location.Y);

                if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
                {
                    backColor = SystemColors.Highlight;

                    foreColor = SystemColors.HighlightText;
                }

                else if ((e.State & TreeNodeStates.Hot) == TreeNodeStates.Hot)
                {
                    backColor = SystemColors.HotTrack;

                    foreColor = SystemColors.HighlightText;
                }

                else
                {
                    backColor = e.Node.BackColor;

                    foreColor = e.Node.ForeColor;
                }
                using (SolidBrush brush = new SolidBrush(backColor))
                {
                    e.Graphics.FillRectangle(brush, nodeBounds);
                }
                TextRenderer.DrawText(e.Graphics, e.Node.Text, this.TvGUI.Font, nodeBounds, foreColor, backColor);

                if ((e.State & TreeNodeStates.Focused) == TreeNodeStates.Focused)
                {
                    ControlPaint.DrawFocusRectangle(e.Graphics, nodeBounds, foreColor, backColor);
                }
                e.DrawDefault = false;
            }
            else
            {
                e.DrawDefault = true;
            }
        }

        void TvGUI_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (suppressEvents)
                return;
            if (e.Node.Parent != null)
                return;

            object o = Enum.Parse(typeof(PPPoETag.TagType), e.Node.Text);
            if (o == null)
            {
                return;
            }

            PPPoETag.TagType pt = (PPPoETag.TagType)o;

            if (!e.Node.Checked)
            {
                if (tags.ContainsKey(pt))
                {
                    tags[pt].IsEnabled = false;
                    tags[pt].IsExpanded = false;
                    suppressEvents = true;
                    e.Node.Checked = false;
                    e.Node.Collapse(false);
                    suppressEvents = false;
                    e.Node.Nodes.Clear();
                }
            }
            else
            {
                if (!tags.ContainsKey(pt))
                {
                    AddTag(pt, (byte[])null);
                }
                else
                {
                    tags[pt].IsEnabled = true;
                }

                if (e.Action == TreeViewAction.Expand)
                {
                    tags[pt].IsExpanded = true;
                }
        
            }

            UpdateTreeView();
        }

        void TvGUI_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (suppressEvents)
                return;

            TreeNode parent = e.Node.Parent;
            if (parent == null)
                return;

            PPPoETag.TagType tt = (PPPoETag.TagType)Enum.Parse(typeof(PPPoETag.TagType), parent.Text);

            if (tags.ContainsKey(tt))
            {
                UI.HexDataModyfiingDialog editor = new UI.HexDataModyfiingDialog(tags[tt].ToString(), tags[tt].Data);
                if (editor.ShowDialog(MainWindow.Instance) == DialogResult.OK)
                {
                    suppressEvents = true;
                    tags.Remove(tt);
                    AddTag(tt, editor.HexValues);

                    if (tt == PPPoETag.TagType.ACName)
                    {
                        StringBuilder sb = new StringBuilder();
                        foreach (byte b in editor.HexValues)
                        {
                            sb.Append((char)b);
                        }
                        ACName.StringContainer.Text = sb.ToString();
                    }
                    else if (tt == PPPoETag.TagType.HostUniq)
                    {
                        HostUniqueId.StringContainer.Text = ByteUtils.ByteArrToString(editor.HexValues);
                    }

                    suppressEvents = false;

                    UpdateTreeView();
                }
            }
        }

        void TvGUI_AfterExpand(object sender, TreeViewEventArgs e)
        {
            if (suppressEvents)
                return;

            object o = Enum.Parse(typeof(PPPoETag.TagType), e.Node.Text);
            if (o == null)
            {
                return;
            }
            PPPoETag.TagType pt = (PPPoETag.TagType)o;

            if (tags.ContainsKey(pt))
            {
                tags[pt].IsExpanded = true;
            }
        }

        void TvGUI_AfterCollapse(object sender, TreeViewEventArgs e)
        {

            if (suppressEvents)
                return;

            object o = Enum.Parse(typeof(PPPoETag.TagType), e.Node.Text);
            if (o == null)
            {
                return;
            }
            PPPoETag.TagType pt = (PPPoETag.TagType)o;

            if (tags.ContainsKey(pt))
            {
                tags[pt].IsExpanded = false;
            }
        }

        #endregion
    }
}
